package application.controllers;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

import application.models.datalayer.Date;

/**
 * Handles periods between dates
 * 
 * @author Philip Rasmussen & Morten Rasmussen
 */
public class PeriodController {

	private static final String formatString = "dd/MM/yyyy";
	private static final DateFormat format = new SimpleDateFormat(formatString);
	{
		format.setLenient(false);
	}
	
	private Date from;
	private Date to;
	
	/**
	 * Returns a date object for the current day.
	 */
	public static Date getCurrentDay(){
		
		GregorianCalendar cal = new GregorianCalendar();
		
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		
		return new Date(cal.getTime());
	}
	
	/**
	 * Constructs a new instance. The period is from the current date and X days forward
	 * 
	 * @param daysAfter			How many days from the current day the period should be
	 * @throws PeriodException	if daysAfter was less than 1
	 */
	public PeriodController(int daysAfter) throws PeriodException {
		
		if(daysAfter <= 0) throw new PeriodException("daysAfter was negative");

		Date start = getCurrentDay();

		setFromDate(start);
		
		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(start);
		cal.add(Calendar.DAY_OF_YEAR, daysAfter);
		
		setToDate(new Date(cal.getTime()));
	}
	
	/**
	 * Constructs a new period ranging from "from" date to "to" date
	 * 
	 * @param from				The date the period starts at
	 * @param to				The date the period ends at
	 * @throws PeriodException	If to < from
	 */
	public PeriodController(Date from, Date to) throws PeriodException {
		
		setFromDate(from);
		setToDate(to);
	}
	
	/**
	 * Constructs a new period ranging from "from" date to "to" date
	 * 
	 * @param from				The date the period starts at. The format is DD/MM/YYYY
	 * @param to				The date the period ends at. The format is DD/MM/YYYY
	 * @throws PeriodException	If to < from or the strings are of invalid formats
	 */
	public PeriodController(String from, String to) throws PeriodException {
		
		setFromDate(from);
		setToDate(to);		
	}
	
	/**
	 * Constructs a new period ranging from "from" date to "to" date
	 * 
	 * @param from				The date the period starts at
	 * @param to				The date the period ends at.  The format is DD/MM/YYYY
	 * @throws PeriodException	If to < from or the string is of an invalid format
	 */
	public PeriodController(Date from, String to) throws PeriodException {

		setFromDate(from);
		setToDate(to);		
	}
	
	/**
	 * Constructs a new period ranging from "from" date to "to" date
	 * 
	 * @param from				The date the period starts at
	 * @param to				The date the period ends at. The format is DD/MM/YYYY
	 * @throws PeriodException	If to < from or the string is of an invalid format
	 */
	public PeriodController(String from, Date to) throws PeriodException {

		setFromDate(from);
		setToDate(to);		
	}
	
	/**
	 * Modifies the end date of the period
	 * 
	 * @param to				the new date the period should end at
	 * @throws PeriodException	if to < the periods "from"
	 */
	public void setToDate(Date to) throws PeriodException {

		if(validatePeriod(from, to)) this.to = to;
		else throw new PeriodException("'From' date must come before 'To' date.");
	}
	
	/**
	 * Modifies the end date of the period
	 * 
	 * @param to				the new date the period should end at. The format is DD/MM/YYYY
	 * @throws PeriodException	if to < the periods "from" or the string is of an invalid format
	 */
	public void setToDate(String to) throws PeriodException {
		
		try {
			Date dateTo = parseDateString(to);
			if(validatePeriod(from, dateTo)) this.to = dateTo;
			else throw new PeriodException("'From' date must come before 'To' date.");
		} catch(ParseException e){
			throw new PeriodException("Passed date was of an invalid format.", e);
		}
	}
	
	/**
	 * Modifies the start date of the period
	 * 
	 * @param to				the new date the period should start at
	 * @throws PeriodException	if from > the periods "to"
	 */
	public void setFromDate(Date from) throws PeriodException {
		
		if(validatePeriod(from, to)) this.from = from;
		else throw new PeriodException("'From' date must come before 'To' date.");
	}
	
	/**
	 * Modifies the start date of the period
	 * 
	 * @param to				the new date the period should start at. The format is DD/MM/YYYY
	 * @throws PeriodException	if from > the periods "to" or the string is of an invalid format
	 */
	public void setFromDate(String from) throws PeriodException {
		
		try {
			Date dateFrom = parseDateString(from);
			if(validatePeriod(dateFrom, to)) this.from = dateFrom;
			else throw new PeriodException("'From' date must come before 'To' date.");
		} catch(ParseException e){
			throw new PeriodException("Passed date was of an invalid format.", e);
		}
	}
	
	/**
	 * Returns the start of the period
	 * 
	 * @return	the start of the period as a Date object
	 */
	public Date getFromDate(){
		
		return from;
	}
	
	/**
	 * Returns the end of the period
	 * 
	 * @return	the end of the period as a Date object
	 */
	public Date getToDate(){
		
		return to;
	}
	
	/**
	 * Returns the start of the period
	 * 
	 * @return	the start of the period as a String
	 */
	public String getFromDateAsString(){
		
		return parseDate(from);
	}
	
	/**
	 * Returns the end of the period
	 * 
	 * @return	the end of the period as a String
	 */
	public String getToDateAsString(){
		
		return parseDate(to);
	}
	
	/**
	 * Validates that a given period is valid.
	 * Periods are valid if the period starts before it ends (from < to)
	 * 
	 * @param from	the date the period starts
	 * @param to	the date the period ends
	 * @return	true if the period defined by from and to is valid, otherwise false
	 */
	private boolean validatePeriod(Date from, Date to){
		
		if(from == null || to == null) return true;
		return from.getTime() < to.getTime();
	}
	
	/**
	 * Attempt to parse the given string into a date object using using very strict rules
	 * 
	 * @param dateString		the string to parse - must match the DD/MM/YYYY format.
	 * @return					the Date expressed by the string
	 * @throws ParseException	if the string does not follow the format or cannot be parsed
	 */
	private Date parseDateString(String dateString) throws ParseException {
		
		if(dateString == null) throw new NullPointerException("dateString was null.");
		  
		  // Validate
		  if(dateString.length() > formatString.length() || !dateString.matches("^[0-9]{1,2}/[0-9]{1,2}/[0-9]{1,4}$")) throw new ParseException("The date string did not match the DD/MM/YYYY pattern.", 0);
		
		return new Date(format.parse(dateString));
	}
	
	/**
	 * Parses a date into a date string following the DD/MM/YYYY format
	 * 
	 * @param date	the date to format
	 * @return		the formatted string representing the date
	 */
	private String parseDate(Date date){
		
		return format.format(date);
	}
}
